package com.zdft.promotion.common.acl;

import com.zdft.promotion.common.util.ExceptionUtil;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;

import java.lang.reflect.Method;
import java.util.Map;

/**
 * 控制器访问控制注解处理器
 */
public class ControllerACLAnnotationHandler implements ApplicationListener<ContextRefreshedEvent>
{
    private static final Logger log = LoggerFactory.getLogger(ControllerACLAnnotationHandler.class);

    @Override
    public void onApplicationEvent(ContextRefreshedEvent contextRefreshedEvent)
    {
        ApplicationContext ctx = contextRefreshedEvent.getApplicationContext();
        if (!(ctx instanceof AnnotationConfigServletWebServerApplicationContext))
        {
            log.info("None @Controller used.");
            return;
        }

        AnnotationConfigServletWebServerApplicationContext context = (AnnotationConfigServletWebServerApplicationContext) ctx;
        Map<String, Object> map = ctx.getBeansWithAnnotation(Controller.class);
        for (Map.Entry<String, Object> entry : map.entrySet())
        {
            try
            {
                String name = context.getBeanDefinition(entry.getKey()).getBeanClassName();
                if (StringUtils.isNoneEmpty(name))
                {
                    checkAnnotation(Class.forName(name));
                }
            }
            catch (ClassNotFoundException e)
            {
                log.error(ExceptionUtil.toString(e));
            }
        }
    }

    /**
     * 检查注解
     *
     * @param clazz 控制器
     */
    private void checkAnnotation(Class<?> clazz)
    {
        AccessControl parentAnn = clazz.getDeclaredAnnotation(AccessControl.class);
        RequestMapping parentMappingAnn = clazz.getDeclaredAnnotation(RequestMapping.class);
        String[] parentUrls = null;
        if (null != parentMappingAnn)
        {
            parentUrls = parentMappingAnn.value();
            if (ArrayUtils.isEmpty(parentUrls))
            {
                parentUrls = new String[]{clazz.getSimpleName()};
            }
        }

        Method[] methods = clazz.getMethods();
        for (Method method : methods)
        {
            AccessControl ann = method.getDeclaredAnnotation(AccessControl.class);
            RequestMapping requestAnn = method.getDeclaredAnnotation(RequestMapping.class);
            if (null != requestAnn)
            {
                String[] urls = requestAnn.value();
                if (ArrayUtils.isEmpty(urls))
                {
                    urls = new String[]{method.getName()};
                }
                RequestMethod[] httpMethods = requestAnn.method();
                if (ArrayUtils.isEmpty(httpMethods))
                {
                    handleAnnotation(parentAnn, ann, parentUrls, urls, "GET");
                }
                else
                {
                    for (RequestMethod httpMethod : httpMethods)
                    {
                        handleAnnotation(parentAnn, ann, parentUrls, urls, httpMethod.name().toUpperCase());
                    }
                }
                continue;
            }
            GetMapping getAnn = method.getDeclaredAnnotation(GetMapping.class);
            if (null != getAnn)
            {
                String[] urls = getAnn.value();
                if (ArrayUtils.isEmpty(urls))
                {
                    urls = new String[]{method.getName()};
                }
                handleAnnotation(parentAnn, ann, parentUrls, urls, "GET");
                continue;
            }
            PutMapping putAnn = method.getDeclaredAnnotation(PutMapping.class);
            if (null != putAnn)
            {
                String[] urls = putAnn.value();
                if (ArrayUtils.isEmpty(urls))
                {
                    urls = new String[]{method.getName()};
                }
                handleAnnotation(parentAnn, ann, parentUrls, urls, "PUT");
                continue;
            }
            DeleteMapping deleteAnn = method.getDeclaredAnnotation(DeleteMapping.class);
            if (null != deleteAnn)
            {
                String[] urls = deleteAnn.value();
                if (ArrayUtils.isEmpty(urls))
                {
                    urls = new String[]{method.getName()};
                }
                handleAnnotation(parentAnn, ann, parentUrls, urls, "DELETE");
                continue;
            }
            PostMapping postAnn = method.getDeclaredAnnotation(PostMapping.class);
            if (null != postAnn)
            {
                String[] urls = postAnn.value();
                if (ArrayUtils.isEmpty(urls))
                {
                    urls = new String[]{method.getName()};
                }
                handleAnnotation(parentAnn, ann, parentUrls, urls, "POST");
                continue;
            }
            PatchMapping patchAnn = method.getDeclaredAnnotation(PatchMapping.class);
            if (null != patchAnn)
            {
                String[] urls = patchAnn.value();
                if (ArrayUtils.isEmpty(urls))
                {
                    urls = new String[]{method.getName()};
                }
                handleAnnotation(parentAnn, ann, parentUrls, urls, "PATCH");
            }
        }
    }

    /**
     * 处理注解
     *
     * @param parentAnn  父注解
     * @param ann        方法注解
     * @param parentUrls 父URL
     * @param urls       方法URL
     * @param method     请求方法
     */
    private void handleAnnotation(AccessControl parentAnn, AccessControl ann, String[] parentUrls, String[] urls
            , String method)
    {
        AccessControl accessControlAnn = null;
        if (null != parentAnn)
        {
            accessControlAnn = parentAnn;
        }
        if (null != ann)
        {
            accessControlAnn = ann;
        }
        if (null == accessControlAnn)
        {
            return;
        }

        for (String url : urls)
        {
            url = StringUtils.startsWith(url, "/") ? url : ("/" + url);
            if (ArrayUtils.isEmpty(parentUrls))
            {
                saveAccessControlMetaInfo(accessControlAnn, method, url);
                continue;
            }
            for (String parentUrl : parentUrls)
            {
                parentUrl = StringUtils.startsWith(parentUrl, "/") ? parentUrl : ("/" + parentUrl);
                parentUrl = StringUtils.removeEnd(parentUrl, "/");
                String finalUrl = parentUrl + url;
                saveAccessControlMetaInfo(accessControlAnn, method, finalUrl);
            }
        }
    }

    /**
     * 保存访问控制元信息
     *
     * @param ann    访问控制注解
     * @param method 请求方法
     * @param url    URL
     */
    private void saveAccessControlMetaInfo(AccessControl ann, String method, String url)
    {
        log.info("method:{}, url:{}, acl:{}", method, url, ann.level());
    }
}
